package org.ethereum.net.stats;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicLong;

import org.json.simple.JSONObject;

/**
 * 
 * Time Ranges for Statistics information
 * 
 * @author Shaolin Wu
 * 
 */
public class TimeRangesStats implements Serializable, SingleKPI {

    private static final long serialVersionUID = 1L;

    public static final String RULE = "2,4,8,16,32,64,128,256"; //ms

    private final Range[] ranges;

    private final AtomicLong totalAmount = new AtomicLong();

    private final String kpiName;
    
    private final String description;
    
    /**
     * Creates a new instance.
     * 
     * 
     * @param rule
     *            the rule of time ranks has to be consisted by a set of sequenced numerical values and separated by
     *            comma which looks like 2,4,8,16,32,64,128,256
     * 
     */
    public TimeRangesStats(String rule, String kpiName, String description) {
	    	this.kpiName = kpiName;
	    	this.description = description;
        if (rule == null || rule.trim().length() == 0) {
            ranges = new Range[0];
            return;
        }

        StringTokenizer st = new StringTokenizer(rule, ",");
        int[] values = new int[st.countTokens()];
        int i = 0;
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            values[i++] = Integer.parseInt(token);
        }

        ranges = new Range[values.length + 1];
        ranges[0] = new Range(0, values[0]);
        for (i = 0; i < values.length; i++) {
            ranges[i + 1] = new Range(values[i], ((i + 1 < values.length) ? values[i + 1] : Long.MAX_VALUE));
        }
    }

    public void arrange(long millisecond) {

        totalAmount.incrementAndGet();

        for (int i = 0; i < ranges.length; i++) {
            if (ranges[i].getThresholdLowValue() <= millisecond && millisecond < ranges[i].getThresholdHighValue()) {
                ranges[i].hit();
                break;
            }
        }

    }

    public void clear() {
        totalAmount.set(0);

        for (int i = 0; i < ranges.length; i++) {
            ranges[i].reset();
        }
    }

    public int length() {
        return ranges.length;
    }

    public Range getRange(int index) {
        if (ranges.length >= index && index < ranges.length) {
            return ranges[index];
        }
        return null;
    }

    public Range[] getRanges() {
        return this.ranges;
    }

    public long getThreshold2() {
        if (ranges.length > 0) {
            return ranges[0].getAmount();
        }
        return 0;
    }

    public int getThreshold2Percentage() {
        return calculate(0);
    }

    public long getThreshold4() {
        if (ranges.length > 1) {
            return ranges[1].getAmount();
        }
        return 0;
    }

    public int getThreshold4Percentage() {
        return calculate(1);
    }

    public long getThreshold8() {
        if (ranges.length > 2) {
            return ranges[2].getAmount();
        }
        return 0;
    }

    public int getThreshold8Percentage() {
        return calculate(2);
    }

    public long getThreshold16() {
        if (ranges.length > 3) {
            return ranges[3].getAmount();
        }
        return 0;
    }

    public int getThreshold16Percentage() {
        return calculate(3);
    }

    public long getThreshold32() {
        if (ranges.length > 4) {
            return ranges[4].getAmount();
        }
        return 0;
    }

    public int getThreshold32Percentage() {
        return calculate(4);
    }

    public long getThreshold64() {
        if (ranges.length > 5) {
            return ranges[5].getAmount();
        }
        return 0;
    }

    public int getThreshold64Percentage() {
        return calculate(5);
    }

    public long getThreshold128() {
        if (ranges.length > 6) {
            return ranges[6].getAmount();
        }
        return 0;
    }

    public int getThreshold128Percentage() {
        return calculate(6);
    }

    public long getThreshold256() {
        if (ranges.length > 7) {
            return ranges[7].getAmount();
        }
        return 0;
    }

    public int getThreshold256Percentage() {
        return calculate(7);
    }

    public long getThresholdGreatThan256() {
        if (ranges.length > 8) {
            return ranges[8].getAmount();
        }
        return 0;
    }

    public int getThresholdGreatThan256Percentage() {
        return calculate(8);
    }

    private int calculate(int index) {
        if (totalAmount.intValue() > 0 && ranges.length > index && ranges[index].getAmount() != 0) {
            BigDecimal big = new BigDecimal(ranges[index].getAmount() / totalAmount.doubleValue() * 100);
            // big.setScale(0, BigDecimal.ROUND_HALF_UP); Useless Operation On Immutable by Sonar Validation
            return big.intValue();
        }
        return 0;
    }

    @Override
    public String toString() {
        if (ranges.length == 0) {
            return "No ranges are being configured currently!";
        }

        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < ranges.length; i++) {
            sb.append(ranges[i].toString()).append("\n");
        }
        return sb.toString();
    }

    static class Range implements Serializable {

        private static final long serialVersionUID = 1L;

        protected final long threshold_low_value;

        protected final long threshold_high_value;

        private final AtomicLong amount = new AtomicLong();

        public Range(long minValue, long maxValue) {
            this.threshold_low_value = minValue;
            this.threshold_high_value = maxValue;
        }

        public long getThresholdLowValue() {
            return threshold_low_value;
        }

        public long getThresholdHighValue() {
            return threshold_high_value;
        }

        public long getAmount() {
            return amount.get();
        }

        public void reset() {
            amount.set(0);
        }

        public void hit() {
            amount.incrementAndGet();
        }

        @Override
        public String toString() {
            return "[interval:(" + threshold_low_value + " -- " + threshold_high_value + ")ms, amount:(" + getAmount()
                    + ")]";
        }
    }

	@Override
	public String getKpiName() {
		return kpiName;
	}

	@Override
	public void reset() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public JSONObject toJson() {
		// TODO Auto-generated method stub
		return null;
	}

}